Skip to content

fix(sdk): improvements in default sandbox.write and sandbox.read implementations#2321

Merged
Eugene Yurtsev (eyurtsev) merged 12 commits intomainfrom
eugene/fix_sandbox_api
Apr 3, 2026
Merged

fix(sdk): improvements in default sandbox.write and sandbox.read implementations#2321
Eugene Yurtsev (eyurtsev) merged 12 commits intomainfrom
eugene/fix_sandbox_api

Conversation

@eyurtsev
Copy link
Copy Markdown
Collaborator

@eyurtsev Eugene Yurtsev (eyurtsev) commented Mar 30, 2026

  • Let unhandelled exceptions to bubble up, instead of present them to the LLM.
  • Absorb responsibility of creating parent folder to upload_files so it can be optimized.
  • Better handling for large files when doing a read (to include truncation and pagination).
  • Tested with full integration suite with Daytona and LangSmith Sandbox

@github-actions github-actions bot added deepagents Related to the `deepagents` SDK / agent harness fix A bug fix (PATCH) internal User is a member of the `langchain-ai` GitHub organization size: XS < 50 LOC labels Mar 30, 2026
@eyurtsev Eugene Yurtsev (eyurtsev) changed the title fix(sdk): fix default write implementation fix(sdk): fix excessive exception handling in sandbox.write Mar 30, 2026
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq bot commented Mar 30, 2026

Merging this PR will not alter performance

✅ 32 untouched benchmarks
⏩ 15 skipped benchmarks1


Comparing eugene/fix_sandbox_api (fd3c1fb) with main (9d3eadf)

Open in CodSpeed

Footnotes

  1. 15 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@github-actions github-actions bot added size: S 50-199 LOC and removed size: XS < 50 LOC labels Apr 2, 2026
@eyurtsev Eugene Yurtsev (eyurtsev) marked this pull request as ready for review April 3, 2026 17:11
Copilot AI review requested due to automatic review settings April 3, 2026 17:11
@eyurtsev Eugene Yurtsev (eyurtsev) changed the title fix(sdk): fix excessive exception handling in sandbox.write fix(sdk): improvements in default sandbox.write and sandbox.read implementations Apr 3, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the sandbox backend to reduce overly-defensive exception handling in write(), and improves read() behavior to better handle large outputs returned via execute().

Changes:

  • Update BaseSandbox.read()’s server-side script to cap text output size and add truncation guidance; add binary-preview size limits.
  • Simplify BaseSandbox.write() by letting backend exceptions bubble up and changing behavior when upload_files() returns an empty response.
  • Update/adjust unit tests to cover truncated read output and align write success assertions.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
libs/deepagents/deepagents/backends/sandbox.py Adjusts the server-side read script (truncation + binary handling) and changes write/upload error handling + docs.
libs/deepagents/tests/unit_tests/backends/test_sandbox_backend.py Adds coverage for truncated read content and updates write success expectations.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 243 to 247
except UnicodeDecodeError:
with open(path, 'rb') as f:
raw = f.read()
print(json.dumps({{'encoding': 'base64', 'content': base64.b64encode(raw).decode('ascii')}}))
sys.exit(0)
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the non-UTF-8 fallback, the script reads and base64-encodes the entire file without applying the MAX_BINARY_BYTES guard. Since _get_file_type() defaults to text for unknown extensions, large binary files can hit this path and exceed stdout/transport limits. Consider checking os.path.getsize(path) against MAX_BINARY_BYTES before f.read() here (and returning a clear error) to keep the size cap effective for all binary outputs.

Copilot uses AI. Check for mistakes.
Comment on lines +284 to +286
if returned_lines == 0 and not truncated:
print(json.dumps({{'error': 'Line offset ' + str(offset) + ' exceeds file length (' + str(line_count) + ' lines)'}}))
sys.exit(0)
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The returned_lines == 0 check currently reports an "offset exceeds file length" error even when limit == 0 (the loop exits immediately). That makes limit=0 behave like an invalid offset. Consider handling limit <= 0 explicitly (e.g., return empty content) and/or include limit in the error condition so offset errors only occur when offset is actually beyond EOF.

Copilot uses AI. Check for mistakes.
Comment on lines +467 to +469
# An unreachable condition was reached
msg = f"Responses was expected to return 1 result, but it returned {len(responses)} with type {type(responses)}"
raise AssertionError(msg)
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

write() now raises an AssertionError when upload_files() returns an empty list. This can crash callers in production for what is effectively an integration/backend contract violation, and it also removes the prior structured WriteResult(error=...) behavior. Consider returning a WriteResult error (or raising a dedicated exception type) with actionable context, rather than asserting.

Suggested change
# An unreachable condition was reached
msg = f"Responses was expected to return 1 result, but it returned {len(responses)} with type {type(responses)}"
raise AssertionError(msg)
msg = (
f"Failed to write file '{file_path}': upload_files() returned no responses "
f"for 1 requested upload (got {len(responses)}; type={type(responses).__name__})"
)
return WriteResult(error=msg)

Copilot uses AI. Check for mistakes.
Comment on lines 729 to +733
Implementations must support partial success - catch exceptions per-file
and return errors in `FileUploadResponse` objects rather than raising.

Upload files is responsible for ensuring that the parent path exists
(if user permissions allow the user to write to the given directory)
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This docstring says upload_files() is responsible for ensuring the parent path exists, but BaseSandbox.write() still runs _WRITE_CHECK_TEMPLATE which calls os.makedirs(...) (and the comment above write() still says "Existance check + mkdir"). Either move the mkdir responsibility fully into upload_files() as stated, or update the docs/comments/PR description to reflect the actual behavior to avoid double work and confusion.

Copilot uses AI. Check for mistakes.
@eyurtsev Eugene Yurtsev (eyurtsev) enabled auto-merge (squash) April 3, 2026 17:16
@eyurtsev Eugene Yurtsev (eyurtsev) merged commit 4a37a46 into main Apr 3, 2026
52 checks passed
@eyurtsev Eugene Yurtsev (eyurtsev) deleted the eugene/fix_sandbox_api branch April 3, 2026 17:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

deepagents Related to the `deepagents` SDK / agent harness fix A bug fix (PATCH) internal User is a member of the `langchain-ai` GitHub organization size: S 50-199 LOC

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants